﻿using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.Xaml.Bulbs;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Extensibility.Menu;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Xaml.Impl;
using JetBrains.ReSharper.Psi.Xaml.Impl.Tree;
using JetBrains.ReSharper.Psi.Xaml.Impl.Util;
using JetBrains.ReSharper.Psi.Xaml.Tree;
using JetBrains.ReSharper.Psi.Xml.Impl.Tree;
using JetBrains.ReSharper.Psi.Xml.Tree;
using JetBrains.Util;
using MugenMvvmToolkit.ReSharper.Infrastructure;

namespace MugenMvvmToolkit.ReSharper.Views.ContextActions
{
    [ContextAction(Name = "DesignDataContextAction", Group = "XAML", Priority = 100)]
    public class DesignDataContextAction : ContextActionImplBase<XamlContextActionDataProvider>
    {
        #region Fields

        private IClass _class;
        private XmlTagHeaderNode _header;
        private IXamlObjectElementDeclaration _headerObjectElement;
        private IList<IDeclaredType> _viewModels;

        #endregion

        #region Constructors

        public DesignDataContextAction([NotNull] XamlContextActionDataProvider provider)
            : base(provider)
        {
        }

        #endregion

        #region Overrides of ContextActionBase

        public override IEnumerable<IntentionAction> CreateBulbItems()
        {
            return _viewModels
                .Select(type => new DelegateBulbAction((solution, indicator) => Execute(type),
                            string.Format(UiResources.AddDesignDataContextFormat, type.GetClrName().ShortName)))
                .ToContextAction();
        }

        /// <summary>
        ///     Check if this action is available at the constructed context.
        ///     Actions could store precalculated info in <paramref name="cache" /> to share it between different actions
        /// </summary>
        /// <returns>
        ///     true if this bulb action is available, false otherwise.
        /// </returns>
        public override bool IsAvailable(IUserDataHolder cache)
        {
            _header = TryFindHeaderNode();
            if (_header == null)
                return false;
            _headerObjectElement = XamlObjectElementNavigator.GetByTagHeader(_header) as IXamlObjectElementDeclaration;
            if (_headerObjectElement == null)
                return false;
            _class = _headerObjectElement.DeclaredElement as IClass;
            if (_class == null)
                return false;

            INamespaceAlias namespaceAlias = GetOrCreateDesignAlias(null);
            if (namespaceAlias != null)
            {
                IPropertyAttribute dataContextProperty = _header
                    .Attributes
                    .OfType<IPropertyAttribute>()
                    .FirstOrDefault(attribute => attribute.XmlNamespace == namespaceAlias.XmlName && attribute.XmlName == "DataContext");
                if (dataContextProperty != null)
                    return false;
            }
            _viewModels = TypeProvider
                .FindViewModelsForView(_class)
                .Where(type => type.IsResolved)
                .ToList();
            return _viewModels.Count != 0;
        }

        #endregion

        #region Methods

        private void Execute(IDeclaredType viewModelType)
        {
            XamlElementFactory xamlElementFactory = XamlElementFactory.GetInstance(Provider.PsiModule, true);
            INamespaceAlias namespaceAlias = GetOrCreateDesignAlias(xamlElementFactory);
            IXmlAttribute propertyAnchor = GetPropertyAnchor();

            IPropertyAttribute dummyProperty = xamlElementFactory.CreatePropertyAttribute(_headerObjectElement,
                "DataContext", qualifier: viewModelType);
            dummyProperty = _headerObjectElement.AddAttributeBefore(dummyProperty, propertyAnchor);
            string xmlName = dummyProperty.XmlNamespace;
            _headerObjectElement.RemoveAttribute(dummyProperty);

            string propertyName = string.Format("{0}:DataContext", namespaceAlias.XmlName);
            string value = string.Format("{{{0}:DesignInstance {1}:{2}}}", namespaceAlias.XmlName, xmlName,
                viewModelType.GetClrName().ShortName);
            IPropertyAttribute propertyAttribute = xamlElementFactory.CreatePropertyAttribute(_headerObjectElement,
                propertyName, value);
            _headerObjectElement.AddAttributeBefore(propertyAttribute, propertyAnchor);
        }

        private INamespaceAlias GetOrCreateDesignAlias(XamlElementFactory factory)
        {
            INamespaceAlias aliasNamespace = _header
                .Attributes
                .OfType<INamespaceAlias>()
                .FirstOrDefault(@alias => alias.UnquotedValue == XamlNamespaceConstants.DesignBlend);
            if (factory == null)
                return aliasNamespace;

            INamespaceAlias mcAlias = GetOrCreateNamespaceAlias(factory, "mc", XamlNamespaceConstants.MarkupCompatibility);
            aliasNamespace = GetOrCreateNamespaceAlias(factory, "d", XamlNamespaceConstants.DesignBlend);

            string ignoarbleName = string.Format("{0}:Ignorable", mcAlias.XmlName);
            var ignorableProperty = _header
                .Attributes
                .OfType<IPropertyAttribute>()
                .FirstOrDefault(attribute => attribute.XmlNamespace == mcAlias.XmlName && attribute.PropertyName == "Ignorable" && attribute.UnquotedValue == "d");
            if (ignorableProperty == null)
            {
                var ignoreProperty = factory.CreatePropertyAttribute(_headerObjectElement, ignoarbleName, "d");
                _headerObjectElement.AddAttributeBefore(ignoreProperty, GetPropertyAnchor());
            }
            return aliasNamespace;
        }

        private INamespaceAlias GetOrCreateNamespaceAlias(XamlElementFactory factory, string name, string @namespace)
        {
            INamespaceAlias aliasNamespace = _header
                .Attributes
                .OfType<INamespaceAlias>()
                .FirstOrDefault(@alias => alias.UnquotedValue == @namespace);
            if (aliasNamespace != null)
                return aliasNamespace;
            var namespaceAlias = factory.CreateNamespaceAlias(name, @namespace);
            return _headerObjectElement.AddNamespaceAlias(namespaceAlias);
        }

        private IXmlAttribute GetPropertyAnchor()
        {
            return _header.Attributes.OfType<IPropertyAttribute>().FirstOrDefault();
        }

        private XmlTagHeaderNode TryFindHeaderNode()
        {
            ITreeNode treeNode = Provider.SelectedElement;
            if (treeNode == null)
                return null;
            return treeNode as XmlTagHeaderNode ?? treeNode.Parent as XmlTagHeaderNode;
        }

        #endregion
    }
}